module dgl.Event;

version(Tango)
{
	version(DFL_TANGO097rc1)
	{
		version = DFL_TANGObefore099rc3;
		version = DFL_TANGObefore0994;
	}
	else version(DFL_TANGO098rc2)
	{
		version = DFL_TANGObefore099rc3;
		version = DFL_TANGObefore0994;
	}
	else version(DFL_TANGObefore099rc3)
	{
		version = DFL_TANGObefore0994;
	}
	else version(DFL_TANGO0992)
	{
		version = DFL_TANGObefore0994;
	}
	else version(DFL_TANGO0993)
	{
		version = DFL_TANGObefore0994;
	}
	else version(DFL_TANGO_0994)
	{
	}
	
	
	public import tango.core.Thread;
	
	public import tango.core.Traits;
		
	
	template PhobosTraits()
	{
		static if(!is(ParameterTypeTuple!(function() { })))
		{
			// Grabbed from std.traits since Tango's meta.Traits lacks these:
			
			template ParameterTypeTuple(alias dg)
			{
				alias ParameterTypeTuple!(typeof(dg)) ParameterTypeTuple;
			}
			
			/** ditto */
			template ParameterTypeTuple(dg)
			{
				static if (is(dg P == function))
					alias P ParameterTypeTuple;
				else static if (is(dg P == delegate))
					alias ParameterTypeTuple!(P) ParameterTypeTuple;
				else static if (is(dg P == P*))
					alias ParameterTypeTuple!(P) ParameterTypeTuple;
				else
					static assert(0, "argument has no parameters");
			}
		}
	}
	
	mixin PhobosTraits;
	
	
	char[] getObjectString(Object o)
	{
		version(DFL_TANGObefore0994)
		{
			return o.toUtf8();
		}
		else
		{
			return o.toString();
		}
	}
	
	
	private import tango.core.Memory;
	
	void gcPin(void* p) { }
	void gcUnpin(void* p) { }
	
	void gcGenCollect()
	{
		version(DFL_TANGObefore099rc3)
			gc.collect();
		else
			GC.collect();
	}
	
	void gcFullCollect()
	{
		version(DFL_TANGObefore099rc3)
			gc.collect();
		else
			GC.collect();
	}
	
	
	private import tango.text.Ascii;
	
	alias tango.text.Ascii.icompare stringICmp;
	
	dchar utf32charToLower(dchar dch)
	{
		// TO-DO: fix; not just ASCII.
		if(dch >= 0x80)
			return dch;
		char[1] input, result;
		input[0] = dch;
		return tango.text.Ascii.toLower(input, result)[0];
	}
	
	
	private import tango.stdc.stringz;
	
	alias tango.stdc.stringz.fromStringz stringFromStringz;
	
	version(DFL_TANGObefore0994)
	{
		alias tango.stdc.stringz.toUtf8z stringToStringz;
	}
	else
	{
		alias tango.stdc.stringz.toStringz stringToStringz;
	}
	
	
	private import tango.io.FilePath;
	
	char[] pathGetDirName(char[] s)
	{
		scope mypath = new FilePath(s);
		return mypath.path();
	}
	
	char[] pathJoin(char[] p1, char[] p2)
	{
		return FilePath.join(p1, p2);
	}
	
	
	private import tango.core.Exception;
	
	class OomException: tango.core.Exception.OutOfMemoryException
	{
		this()
		{
			super(null, 0);
		}
	}
	
	
	private import tango.text.convert.Utf;
	
	dchar utf8stringGetUtf32char(char[] input, inout uint idx)
	{
		// Since the 'ate' (x) param is specified, the output (result) doesn't grow and returns when full.
		dchar[1] result;
		uint x;
		version(DFL_TANGObefore0994)
		{
			tango.text.convert.Utf.toUtf32(input[idx .. input.length], result, &x);
		}
		else
		{
			tango.text.convert.Utf.toString32(input[idx .. input.length], result, &x);
		}
		idx += x;
		return result[0];
	}
	
	version(DFL_TANGObefore0994)
	{
		alias tango.text.convert.Utf.toUtf8 utf16stringtoUtf8string;
	}
	else
	{
		alias tango.text.convert.Utf.toString utf16stringtoUtf8string;
	}
	
	version(DFL_TANGObefore0994)
	{
		alias tango.text.convert.Utf.toUtf16 utf8stringtoUtf16string;
	}
	else
	{
		alias tango.text.convert.Utf.toString16 utf8stringtoUtf16string;
	}
	
	wchar* utf8stringToUtf16stringz(char[] s)
	{
		wchar[] ws;
		version(DFL_TANGObefore0994)
		{
			ws = tango.text.convert.Utf.toUtf16(s);
		}
		else
		{
			ws = tango.text.convert.Utf.toString16(s);
		}
		ws ~= '\0';
		return ws.ptr;
	}
	
	version(DFL_TANGObefore0994)
	{
		alias tango.text.convert.Utf.toUtf8 utf32stringtoUtf8string;
	}
	else
	{
		alias tango.text.convert.Utf.toString utf32stringtoUtf8string;
	}
	
	version(DFL_TANGObefore0994)
	{
		alias tango.text.convert.Utf.toUtf32 utf8stringtoUtf32string;
	}
	else
	{
		alias tango.text.convert.Utf.toString32 utf8stringtoUtf32string;
	}
	
	
	private import tango.io.FileConst;
	
	alias tango.io.FileConst.FileConst.NewlineString nativeLineSeparatorString;
	
	alias tango.io.FileConst.FileConst.PathSeparatorString nativePathSeparatorString;
	
	
	private import tango.text.Util;
	
	alias tango.text.Util.delimit!(char) stringSplit;
	
	int charFindInString(char[] str, dchar dch)
	{
		//uint locate(T, U=uint) (T[] source, T match, U start=0)
		uint loc;
		loc = tango.text.Util.locate!(char)(str, dch);
		if(loc == str.length)
			return -1;
		return cast(int)loc;
	}
	
	alias tango.text.Util.splitLines!(char) stringSplitLines;
	
	
	private import tango.text.convert.Integer;
	
	alias tango.text.convert.Integer.toInt!(char) stringToInt;
	
	version(DFL_TANGObefore0994)
	{
		alias tango.text.convert.Integer.toUtf8 stringToInt;
	}
	else
	{
		alias tango.text.convert.Integer.toString stringToInt;
	}
	
	char[] uintToHexString(uint num)
	{
		char[16] buf;
		return tango.text.convert.Integer.format!(char, uint)(buf, num,
			tango.text.convert.Integer.Style.HexUpper).dup;
	}
	
	char[] intToString(int num)
	{
		char[16] buf;
		return tango.text.convert.Integer.format!(char, uint)(buf, num).dup;
	}
	
	
	private import tango.stdc.ctype;
	
	int charIsHexDigit(dchar dch)
	{
		return dch < 0x80 && tango.stdc.ctype.isxdigit(cast(char)dch);
	}
	
	
	private import tango.io.model.IConduit;
	
	version(DFL_DSTREAM_ICONDUIT) // Disabled by default.
	{
		alias tango.io.model.IConduit.IConduit DStream; // Requires writability.
	}
	else
	{
		alias tango.io.model.IConduit.InputStream DStream;
	}
	
	alias tango.io.model.IConduit.OutputStream DOutputStream;
	
	alias tango.io.model.IConduit.IConduit.Seek DSeekStream;
	
	alias tango.core.Exception.IOException DStreamException; // Note: from tango.core.Exception.
	
	
	class DObject
	{
		version(DFL_TANGObefore0994)
		{
			//alias toUtf8 toString; // Doesn't let you override.
			char[] toString() { return super.toUtf8(); }
			override char[] toUtf8() { return toString(); }
		}
		else
		{
			// No need to override.
		}
	}
}


char* unsafeToStringz(char[] s)
{
	if(!s.ptr[s.length])
		return s.ptr;
	return stringToStringz(s);
}


//Create an event handler; old style.
template Event(TArgs : EventArgs = EventArgs) // docmain
{
	alias Event!(Object, TArgs) Event;
}


/** Managing event handlers.
    Params:
		T1 = the sender type.
		T2 = the event arguments type.
**/
template Event(T1, T2) // docmain
{
	/// Managing event handlers.
	struct Event // docmain
	{
		alias void delegate(T1, T2) Handler; /// Event handler type.
		
		
		/// Add an event handler with the exact type.
		void addHandlerExact(Handler handler)
		in
		{
			assert(handler);
		}
		body
		{
			if(!_array.length)
			{
				_array = new Handler[2];
				_array[1] = handler;
				unsetHot();
			}
			else
			{
				if(!isHot())
				{
					_array ~= handler;
				}
				else // Hot.
				{
					_array = _array ~ (&handler)[0 .. 1]; // Force duplicate.
					unsetHot();
				}
			}
		}
		
		
		/// Add an event handler with parameter contravariance.
		void addHandler(TDG)(TDG handler)
		in
		{
			assert(handler);
		}
		body
		{
			mixin _validateHandler!(TDG);
			
			addHandlerExact(cast(Handler)handler);
		}
		
		
		/// Shortcut for addHandler().
		void opCatAssign(TDG)(TDG handler)
		{
			addHandler!(TDG)(handler);
		}
		
		
		/// Remove the specified event handler with the exact Handler type.
		void removeHandlerExact(Handler handler)
		{
			if(!_array.length)
				return;
			
			size_t iw;
			for(iw = 1; iw != _array.length; iw++)
			{
				if(handler == _array[iw])
				{
					if(iw == 1 && _array.length == 2)
					{
						_array = null;
						break;
					}
					
					if(iw == _array.length - 1)
					{
						_array[iw] = null;
						_array = _array[0 .. iw];
						break;
					}
					
					if(!isHot())
					{
						_array[iw] = _array[_array.length - 1];
						_array[_array.length - 1] = null;
						_array = _array[0 .. _array.length - 1];
					}
					else // Hot.
					{
						_array = _array[0 .. iw] ~ _array[iw + 1 .. _array.length]; // Force duplicate.
						unsetHot();
					}
					break;
				}
			}
		}
		
		
		/// Remove the specified event handler with parameter contravariance.
		void removeHandler(TDG)(TDG handler)
		{
			mixin _validateHandler!(TDG);
			
			removeHandlerExact(cast(Handler)handler);
		}
		
		
		/// Fire the event handlers.
		void opCall(T1 v1, T2 v2)
		{
			if(!_array.length)
				return;
			setHot();
			
			Handler[] local;
			local = _array[1 .. _array.length];
			foreach(Handler handler; local)
			{
				handler(v1, v2);
			}
			
			if(!_array.length)
				return;
			unsetHot();
		}
		
		
		///
		int opApply(int delegate(Handler) dg)
		{
			if(!_array.length)
				return 0;
			setHot();
			
			int result = 0;
			
			Handler[] local;
			local = _array[1 .. _array.length];
			foreach(Handler handler; local)
			{
				result = dg(handler);
				if(result)
					break;
			}
			
			if(_array.length)
				unsetHot();
			
			return result;
		}
		
		
		///
		bool hasHandlers() // getter
		{
			return _array.length > 1;
		}
		
		
		// Use opApply and hasHandlers instead.
		deprecated Handler[] handlers() // getter
		{
			if(!hasHandlers)
				return null;
			return _array[1 .. _array.length].dup; // Because _array can be modified. Function is deprecated anyway.
		}
		
		
		private:
		Handler[] _array; // Not what it seems.
		
		
		void setHot()
		{
			assert(_array.length);
			_array[0] = cast(Handler)&setHot; // Non-null, GC friendly.
		}
		
		
		void unsetHot()
		{
			assert(_array.length);
			_array[0] = null;
		}
		
		
		Handler isHot()
		{
			assert(_array.length);
			return _array[0];
		}
		
		
		// Thanks to Tomasz "h3r3tic" Stachowiak for his assistance.
		template _validateHandler(TDG)
		{
			static assert(is(TDG == delegate), "DFL: Event handler must be a delegate");
			
			alias ParameterTypeTuple!(TDG) TDGParams;
			static assert(TDGParams.length == 2, "DFL: Event handler needs exactly 2 parameters");
			
			static if(is(TDGParams[0] : Object))
			{
				static assert(is(T1: TDGParams[0]), "DFL: Event handler parameter 1 type mismatch");
			}
			else
			{
				static assert(is(T1 == TDGParams[0]), "DFL: Event handler parameter 1 type mismatch");
			}
			
			static if(is(TDGParams[1] : Object))
			{
				static assert(is(T2 : TDGParams[1]), "DFL: Event handler parameter 2 type mismatch");
			}
			else
			{
				static assert(is(T2 == TDGParams[1]), "DFL: Event handler parameter 2 type mismatch");
			}
		}
	}
}


/// Base event arguments.
class EventArgs // docmain
{
	/+
	private static byte[] buf;
	private import std.gc;
	
	
	new(uint sz)
	{
		void* result;
		
		// synchronized // Slows it down a lot.
		{
			if(sz > buf.length)
				buf = new byte[100 + sz];
			
			result = buf[0 .. sz];
			buf = buf[sz .. buf.length];
		}
		
		// std.gc.addRange(result, result + sz); // So that it can contain pointers.
		return result;
	}
	+/
	
	
	/+
	delete(void* p)
	{
		std.gc.removeRange(p);
	}
	+/
	
	
	//private static EventArgs _e;
	private static const EventArgs _e;
	
	
	static this()
	{
		_e = new EventArgs;
	}
	
	
	/// Property: get a reusable, _empty EventArgs.
	static EventArgs empty() // getter
	{
		return _e;
	}
}


// Simple event handler.
alias Event!() EventHandler; // deprecated


///
class ThreadExceptionEventArgs: EventArgs
{
	///
	// The exception that occured.
	this(Object theException)
	{
		except = theException;
	}
	
	
	///
	final Object exception() // getter
	{
		return except;
	}
	
	
	private:
	Object except;
}


alias Event!(ThreadExceptionEventArgs) ThreadExceptionEventHandler; // deprecated

